﻿using System;
using System.Collections.Generic;
using System.Linq;
using Sendz.Domain;
using System.Text;

namespace Sendz.Model
{
    public class EntityFormRepository : IFormRepository
    {
        #region Forms ///////////////////////////////

        public List<Form> GetFormsByUserKey(Guid userKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.Forms.Where(f => f.UserKey == userKey && f.IsRemoved == false).OrderByDescending(f => f.LastModified).ToList();
            }
        }

        public Form GetFormByKey(Guid formKey)
        {
            using (SendzContext db = new SendzContext())
            {
                var thisForm = db.Forms.Single(f => f.FormKey == formKey && f.IsRemoved == false);

                // Build xref lists
                thisForm.FormFields = db.FormFields.Where(ff => ff.FormKey == formKey && ff.IsRemoved == false).OrderBy(ff => ff.SortOrder).ToList();
                // Append the ListEnum value used for select lists (used for validation)
                foreach (var field in thisForm.FormFields)
                {
                    // Some properties are copied from the FieldType definition and stored at the Field level to be used for validation, etc.
                    var thisFieldType = db.FieldTypes.Single(ft => ft.FieldTypeKey == field.FieldTypeKey);
                    field.ListEnum = thisFieldType.ListEnum;
                    field.RegEx = thisFieldType.RegEx;
                    field.RegExError = thisFieldType.RegExError;
                }
                return thisForm;
            }
        }

        public Form GetFormByShortPath(string shortpath)
        {
            using (SendzContext db = new SendzContext())
            {
                var thisForm = db.Forms.FirstOrDefault(f => f.ShortPath == shortpath && f.IsRemoved == false);

                if (thisForm != null)
                {
                    // Build xref lists
                    thisForm.FormFields = db.FormFields.Where(ff => ff.FormKey == thisForm.FormKey && ff.IsRemoved == false).OrderBy(ff => ff.SortOrder).ToList();
                    // Append the ListEnum value used for select lists (used for validation)
                    foreach (var field in thisForm.FormFields)
                    {
                        // Some properties are copied from the FieldType definition and stored at the Field level to be used for validation, etc.
                        var thisFieldType = db.FieldTypes.Single(ft => ft.FieldTypeKey == field.FieldTypeKey);
                        field.ListEnum = thisFieldType.ListEnum;
                        field.RegEx = thisFieldType.RegEx;
                        field.RegExError = thisFieldType.RegExError;
                    }
                    return thisForm;
                }
                return null;
            }
        }

        // Same method for creation and save
        public bool SaveForm(Form form)
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    var thisForm = db.Forms.FirstOrDefault(f => f.FormKey == form.FormKey);
                    if (thisForm != null)
                    {
                        thisForm.Name = form.Name;
                        thisForm.IsRedirectOnComplete = form.IsRedirectOnComplete;
                        thisForm.RedirectURL = form.RedirectURL;
                        thisForm.CompletionMessage = form.CompletionMessage;
                        thisForm.AlertOnComplete = form.AlertOnComplete;
                        // Conditionally update the associated autoresponder
                        var autoResponderKey = Guid.Empty;
                        if (Guid.TryParse(form.AutoResponderKey.ToString(), out autoResponderKey))
                            thisForm.AutoResponderKey = autoResponderKey;
                        else
                            thisForm.AutoResponderKey = null;
                        // Update the last modified date
                        thisForm.LastModified = DateTime.Now;
                    }
                    else
                    {
                        // Update the last modified date so new forms can also appear at top of sort order
                        form.LastModified = DateTime.Now;
                        db.Forms.AddObject(form);
                    }
                    db.SaveChanges();
                    return true;
                }
                catch (Exception ex)
                {
                    throw ex;
                    return false;
                }
            }
        }

        public bool RemoveForm(Guid formKey)
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    var thisForm = db.Forms.Single(f => f.FormKey == formKey);
                    // Logical delete, not physical delete
                    thisForm.IsRemoved = true;
                    // Update the last modified date
                    thisForm.LastModified = DateTime.Now;
                    db.SaveChanges();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }

        public List<Form> GetFormsUsingAutoResponder(Guid autoResponderKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.Forms.Where(f => f.AutoResponderKey == autoResponderKey).ToList();
            }
        }

        public bool RemoveField(Guid formFieldKey)
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    var thisField = db.FormFields.Single(ff => ff.FormFieldKey == formFieldKey);
                    // Logical delete, not physical delete
                    thisField.IsRemoved = true;
                    // Move to end of sort as a secondary reference of removal
                    thisField.SortOrder = 99;
                    // Update the last modified date
                    thisField.LastModified = DateTime.Now;
                    db.SaveChanges();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }

        // Same method for creation and save
        public bool SaveField(Guid formKey, FormField formField)
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    var thisField = db.FormFields.FirstOrDefault(f => f.FormFieldKey == formField.FormFieldKey); // Use so null is thrown when it doesn't exist
                    var thisFieldType = db.FieldTypes.Single(ft => ft.ListEnum == formField.ListEnum);
                    if (thisField != null)
                    {
                        thisField.FormKey = formKey;
                        thisField.Name = formField.Name;
                        // The select list is only ever based on ListEnum so translation is needed (validation reasons)
                        thisField.FieldTypeKey = thisFieldType.FieldTypeKey;
                        thisField.IsRequired = formField.IsRequired;
                        thisField.SortOrder = formField.SortOrder;
                        var options = (formField.Options != null) ? formField.Options : null;
                        thisField.Options = options;
                        // Update the last modified date
                        thisField.LastModified = DateTime.Now;
                    }
                    else
                    {
                        // The select list is only ever based on ListEnum so translation to FieldTypeKey is needed (ListEnum is used for string validation)
                        formField.FieldTypeKey = thisFieldType.FieldTypeKey;
                        db.FormFields.AddObject(formField);
                    }
                    // Refresh the form last updated date when modifying the form's fields (bring to top of the list)
                    var thisForm = db.Forms.Single(f => f.FormKey == formKey);
                    SaveForm(thisForm);
                    db.SaveChanges();
                    return true;
                }
                catch (Exception ex)
                {
                    throw ex;
                    return false;
                }
            }
        }

        public List<FormField> GetFieldsByFormKey(Guid formKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.FormFields.Where(ff => ff.FormKey == formKey && !ff.IsRemoved).OrderBy(ff => ff.SortOrder).ToList();
            }
        }

        public FormField GetFieldByKey(Guid fieldKey)
        {
            using (SendzContext db = new SendzContext())
            {
                var thisField = db.FormFields.FirstOrDefault(ff => ff.FormFieldKey == fieldKey && !ff.IsRemoved);
                // Some properties are copied from the FieldType definition and stored at the Field level to be used for validation, etc.
                if (thisField != null)
                {
                    var thisFieldType = db.FieldTypes.Single(ft => ft.FieldTypeKey == thisField.FieldTypeKey);
                    thisField.ListEnum = thisFieldType.ListEnum;
                    thisField.RegEx = thisFieldType.RegEx;
                    thisField.RegExError = thisFieldType.RegExError;
                }
                return thisField;
            }
        }

        #endregion

        #region Field Types ///////////////////////////////

        public List<FieldType> GetFieldTypes()
        {
            using (SendzContext db = new SendzContext())
            {
                return db.FieldTypes.OrderBy(ft => ft.SortOrder).ToList();
            }
        }

        public FieldType GetFieldTypeByKey(Guid fieldTypeKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.FieldTypes.FirstOrDefault(ft => ft.FieldTypeKey == fieldTypeKey);
            }
        }

        public FieldType GetFieldTypeByListEnum(string listEnum)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.FieldTypes.FirstOrDefault(ft => ft.ListEnum == listEnum);
            }
        }

        #endregion

        #region Responses ///////////////////////////////

        public Guid CreateResponseWithItems(Guid formKey, List<Tuple<Guid, string>> responseItems) 
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    var thisResponse = new Response(formKey);
                    var hasIdentity = false;
                    foreach (var responseItem in responseItems)
                    {
                        // The first response item passed in that is of type '_email' (used in the ListEnum/ControlName) will be used as the identity
                        if (!hasIdentity)
                        {
                            var thisField = db.FormFields.Single(ff => ff.FormFieldKey == responseItem.Item1);
                            var thisFieldType = db.FieldTypes.Single(ft => ft.FieldTypeKey == thisField.FieldTypeKey);
                            // Ensure the email is not passed in as null
                            if (thisFieldType.ListEnum.IndexOf("_email") > -1 && !string.IsNullOrWhiteSpace(responseItem.Item2))
                            {
                                // Todo: need to decide whether it makes sense to keep this in the IIdentityRepository somehow (aggregate model theory per Sanderson)
                                hasIdentity = true;
                                var thisForm = db.Forms.Single(f => f.FormKey == formKey);
                                var identityKey = Guid.Empty;
                                var thisEmail = responseItem.Item2.ToLowerInvariant();
                                // Check to see if this identity already exists (for this user) and get the key if so
                                var thisIdentity = db.Identities.FirstOrDefault(id => id.Email == thisEmail && id.UserKey == thisForm.UserKey);
                                if (thisIdentity != null)
                                    identityKey = thisIdentity.IdentityKey;
                                else
                                {
                                    // Otherwise, create a new identity
                                    thisIdentity = new Identity(thisForm.UserKey, thisEmail);
                                    db.Identities.AddObject(thisIdentity);
                                }
                                // Update the identity of the response
                                thisResponse.IdentityKey = identityKey;
                            }
                        }
                        db.ResponseItems.AddObject(new ResponseItem(thisResponse.ResponseKey, responseItem.Item1, responseItem.Item2));
                    }
                    db.Responses.AddObject(thisResponse);
                    db.SaveChanges();
                    return thisResponse.ResponseKey;
                }
                catch (Exception ex)
                {
                    throw ex;
                    return Guid.Empty;
                }
            }
        }

        public List<Response> GetResponsesByFormKey(Guid formKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.Responses.Where(r => r.FormKey == formKey).OrderByDescending(r => r.CreatedOn).ToList();
            }
        }

        public List<ResponseItem> GetResponseItemsByKey(Guid responseKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.ResponseItems.Where(ri => ri.ResponseKey == responseKey).ToList();
            }
        }

        public Response GetResponseByKey(Guid responseKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.Responses.FirstOrDefault(r => r.ResponseKey == responseKey);
            }
        }

        public Identity GetIdentityByKey(Guid identityKey)
        {
            using (SendzContext db = new SendzContext())
            {
                return db.Identities.FirstOrDefault(i => i.IdentityKey == identityKey);
            }
        }

        public bool LogAutoResponse(Guid autoResponderKey, Guid identityKey)
        {
            using (SendzContext db = new SendzContext())
            {
                try
                {
                    db.AutoResponses.AddObject(new AutoResponse(identityKey, autoResponderKey));
                    db.SaveChanges();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }

        #endregion
    }
}
